﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace PrisonStep
{
    public class BoundingCylinder
    {
        protected float radius;
        protected float height;
        protected Vector3 location;

        public float Y { get { return location.Y; } set { location.Y = value; } }

        /******************************************
         Test 3 cases
         1. The sphere is within the axis of rotation for the cylinder and intersects a side of the cylinder.
         2. The sphere intersects the cylinder at the bottom or top
         3. The sphere intersects the cylinder at a corner
         *******************************************/
        public bool testCollisionSphere(float sphereRadius, Vector3 spherePosition)
        {
            Vector3 tmpCoord = location;

            //test if sphere is within axis of rotation for cylinder and at appropriate height
            //this is basically sphere collision detection
            if ((location.Y <= spherePosition.Y) &&
                location.Y + height >= spherePosition.Y)
            {
                tmpCoord.Y = spherePosition.Y;
                float distance = (tmpCoord - spherePosition).Length();
                return (distance <= sphereRadius + radius);
            }

            //appropriately tests the top or the bottom of the cylinder
            if (location.Y < spherePosition.Y)
                tmpCoord.Y = location.Y + height;
            Vector3 direction = spherePosition - tmpCoord;
            //if the sphere is too far below or above the cylinder it cannot intersect
            if (direction.Y > sphereRadius)
                return false;

            //test the circle (a slab of the cylinder) that is most likely to intersect the cylinder
            //this has been simplified to simply test the intersection of two circles because I am able
            //to take the corresponding circle portion out of both the cylinder and sphere
            float circleSlabRadius = (float) (Math.Pow(sphereRadius, 2) - Math.Pow(tmpCoord.Y - spherePosition.Y, 2));
            spherePosition.Y = tmpCoord.Y;
            float distance2 = (spherePosition - tmpCoord).Length();

            return (radius + circleSlabRadius >= distance2);
        }

        /************************************************************
         This function will determine if a ray will hit this character. 
         The function will also take a radius for the ray. This essentially determines if two cylinders intersect.
         This is solved with simulatenous equations similar to the methods in lecture 11.
         ************************************************************/
        public bool testCollisionRay(Vector3 rayStart, Vector3 rayDirection, float rayRadius)
        {
            //this will determine if any edge of the pie hits the cylinder
            float tmpRadius = rayRadius + radius;

            float m = rayStart.X - location.X;
            float n = rayStart.Z - location.Z;

            float radicand = (float)(
                Math.Pow(tmpRadius, 2) * (Math.Pow(rayDirection.X, 2) + Math.Pow(rayDirection.Z, 2))
                - (Math.Pow(n, 2) * Math.Pow(rayDirection.X, 2)) 
                + 2 * m * n * rayDirection.X * rayDirection.Z -
                (Math.Pow(m, 2) * Math.Pow(rayDirection.Z, 2))
                );

            //if there are no roots
            if (radicand < 0)
                return false;

            //if there is atleast one root (assume a 2nd, it could be a duplicate of the 1st)
            float denominator = (float)(Math.Pow(rayDirection.X, 2) + Math.Pow(rayDirection.Z, 2));
            float t1 = (float)((Math.Sqrt(radicand) - m * rayDirection.X - n * rayDirection.Z)/denominator);
            float t2 = (float)((-Math.Sqrt(radicand) - m * rayDirection.X - n * rayDirection.Z) / denominator);

            //check the y values
            //ensure that if a side of the pie hits the top or the bottom, it still counts as a hit
            float tmpHeight = height + rayRadius;
            float minHeight = 0 - rayRadius;

            if (t1 >= 0)
            {
                float y = rayStart.Y + rayDirection.Y * t1;
                if (y >= minHeight && y <= tmpHeight)
                    return true;
            }
            if (t2 >= 0)
            {
                float y = rayStart.Y + rayDirection.Y * t2;
                if (y >= minHeight && y <= tmpHeight)
                     return true;
            }

            //the point did not lie in the cylinder
            return false;
        }
    }
}
